/*
	DIMGA
	Distributed Island Model Genetic Algorithm 

	Copyright (c) 2006 Nikola Sivacki nsivacki@gmail.com

	This program is free software; you can redistribute it and/or
	modify in under the terms of the GNU General Public Licence as
	published by the Free Software Foundation; either version 2 of
	the Licence, or [at your option] any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAT PURPOSE. See the
	GNU General Public Licence for more details.

	You should have received a copy of the GNU General Public Licence
	along with this program; if not, write to the Free Software 
	Foundation Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
// Vivarium.cpp
#include <stdlib.h>
#include <memory.h>
#include <stdio.h>
#include <time.h>
#include <fstream>
#include <iostream>
#include "vivarium.h"

#include "util/trace.h"

Vivarium::Vivarium(long _lSize, long _lGSize, GAEvaluator* _ev, float _fRatio, unsigned char _iAmount)
{
	m_lSize = _lSize;
	m_lGSize = _lGSize;
	m_ev= _ev;
	m_fRatio=_fRatio;
	m_iAmount=_iAmount;
	m_lBestIndex = -1;

	m_pop = new Genotype*[m_lSize];

	srand(time(0));
	for(long i = 0 ; i < m_lSize ; i++)
	{
		m_pop[i]=new Genotype(_lGSize);
		m_ev->Randomize(m_pop[i]->GetBytes(), m_lGSize);
	}
}

void Vivarium::Randomize()
{
	for(long i = 0 ; i < m_lSize ; i++)
	{
		m_ev->Randomize(m_pop[i]->GetBytes(), m_lGSize);
	}
}

Vivarium::Vivarium(const char* _pcFile, GAEvaluator* _ev, float _fRatio, unsigned char _iAmount)
{
  long _lSize, _lGSize;
  float fRatio;
  unsigned char iAmount;
  unsigned char* GenTemp = NULL;
  int iTemp;

  std::fstream fs(_pcFile);
  if (! fs.is_open())
  {
       error("Cannot open file");
       perror("file open:");
       exit (0);
  }

  fs >> _lSize >> _lGSize;
	m_lSize = _lSize;
	m_lGSize = _lGSize;
	m_ev= _ev;
	m_fRatio=_fRatio;
	m_iAmount=_iAmount;
  m_lBestIndex = -1;
 
	m_pop = new Genotype*[m_lSize];
	for(long i = 0 ; i < m_lSize ; i++)
	{
		m_pop[i]=new Genotype(_lGSize);
	}
	srand(time(0));

//    fprintf(stderr,"Reading vivarium: popsize: %d, gen size: %d\n", m_lSize, m_lGSize);
    GenTemp =  new unsigned char[m_lGSize];
    for (int i = 0 ; i < m_lSize; i++)
    {
        for(int p = 0 ; p < m_lGSize; p++)
        {
            fs >> iTemp; GenTemp[p] = (unsigned char) iTemp;
        }
        m_pop[i]->setGenome(GenTemp);
    }

    fs.close();
    delete[] GenTemp;
}

Vivarium::~Vivarium()
{
	for(long i = 0 ; i < m_lSize ; i++)
	{
		delete m_pop[i];
	}
	delete[] m_pop;
}

void Vivarium::SaveVivarium(const char* file)
{
    unsigned char* GenTemp;
    std::fstream fs(file, std::ios::out );

    fs << m_lSize  << std::endl; 
    fs << m_lGSize << std::endl;

    for(int i = 0 ; i < m_lSize; i++)
    {
        GenTemp = m_pop[i]->GetBytes();
        for(int p = 0 ; p < m_lGSize ; p++)
        {
            fs << ' ' << (int) GenTemp[p];
        }
    }

    fs.close();
}

void Vivarium::Evolve(long _lGens)
{
	for(long i = 0;i<_lGens;i++)
	{
		Mutation();
		Selection(1);
	}

}

void Vivarium::Mutation()
{
  //TRACES("Performing mutation")
	for(long i = 0;i<m_lSize;i++)
	{
		Mutate(m_pop[i], m_fRatio, m_iAmount);
	}
}

void Vivarium::Mutate(Genotype* gSrc, float _fRatio, unsigned char _iAmount)
{
	unsigned char* pGenome 	= gSrc->GetBytes();
	long lSize 		= gSrc->GetSize();
	
//	long iMut =(long) (_fRatio*(float)gSrc->GetSize());
//	long tmp = 0;

  m_ev->Mutate(pGenome, lSize, _fRatio, _iAmount);

//                tmp = 4 * ((rand() % lSize)/4) + 2;
                // 4 is for changing only the last byte in 4-byte values (like floats)
//                pGenome[tmp] +=(rand()%2) ? _iAmount: - _iAmount;

//		pGenome[rand() % lSize] +=(rand()%2) ? _iAmount: - _iAmount;
}

void Vivarium::Combine(Genotype *gSrc, Genotype *gDst)
{
	long lAmount=gSrc->GetSize()/2;
//	long lAmount=gSrc->GetSize();
	if(rand()%2)
//	if(1)
	{
		memcpy(gDst->GetBytes(), gSrc->GetBytes(), lAmount);
	}
	else
	{
		memcpy(gDst->GetBytes() + lAmount, gSrc->GetBytes() + lAmount, lAmount);
	}
}


void Vivarium::Selection(long _lElites)
{
	float fArr[m_lSize];
	long lArr[m_lSize];
	for(long i = 0 ; i  < m_lSize; i++)	
	{
		fArr[i]=  m_pop[i]->GetFitness(m_ev);
		lArr[i]=i;
		
	}

	for(long i =0;i<m_lSize-1;i++)
	{
		for(long j=i+1;j<m_lSize;j++)
		{
			if(fArr[j]<fArr[i])
			{
				float ft=fArr[j];
				fArr[j]=fArr[i];
				fArr[i]=ft;
				
				long lt=lArr[j];
				lArr[j]=lArr[i];
				lArr[i]=lt;
			}
		}
	}

	long elites[_lElites];
	long eliteSizes[_lElites];
	long lTotal=m_lSize-_lElites;

	for(long i =0;i<_lElites;i++)
	{
		elites[i]=lArr[i];
		if(i < _lElites-1)
		{
			lTotal=lTotal/2;
		}
//		TRACESI("elite #", i)
//                TRACESI("elite idx:", elites[i])
//		TRACESI("      dest. pop. size:", lTotal)
//		TRACESF("      fitness:", fArr[i])
		eliteSizes[i]=lTotal;
	}

    m_lBestIndex = elites[0];
    
//    TRACESF("best fitness:", m_pop[m_lBestIndex]->GetFitness(m_ev))
//    TRACESI("best index  :", m_lBestIndex)

	//now we have elites indexes in ascending order in elites
	
	long lDstOffset=_lElites;

	for(long i=0;i<_lElites;i++)
	{
		long leSize=eliteSizes[i];
		for(long j=0;j<leSize;j++)
		{
//		    TRACESII("combining : ", elites[i], lDstOffset+1)
			Combine(m_pop[elites[i]], m_pop[lArr[lDstOffset++]]);
		}
	}
}

void Vivarium::SetGenome(long _iGenome, unsigned char* _pData)
{
    unsigned char * pDest = m_pop[_iGenome]->GetBytes();
    
    for (long i = 0 ; i < m_lSize; i++)
    {
        pDest[i] = _pData[i];
    }
}


Genotype* Vivarium::GetBest()
{
    if(m_lBestIndex != -1)
    {
            return m_pop[m_lBestIndex];
    }else{
        return NULL;
    }
}

void Vivarium::print_genome(Genotype* _g)
{
    unsigned char * pG = _g->GetBytes();
    for(long i = 0 ; i < _g->GetSize(); i++)
    {
        printf("%d ", pG[i]);
    }
    printf(" %2.2f, %ld\n", _g->GetFitness(m_ev), m_lBestIndex);
}

void Vivarium::print_all_genomes()
{

    for(long i = 0 ; i < GetPopSize(); i++)
    {
        print_genome(m_pop[i]);
    }
}
